#include "Cache.h"
#include <limits>
#include "GLTFParser.h"
#include "GeoUtil.h"

std::mutex Cache::s_modelMutex;
std::mutex Cache::s_lineMutex;
CVector<ModelArg> Cache::s_curModelArgs;
CVector<LineArg> Cache::s_curLineArgs;
bool Cache::s_isReceivingDataEnabled = true;

LineArg Cache::s_defLineArg;
ModelArg Cache::s_defModelArg;
ZhicheArg Cache::s_zhicheArg;
CVector<ModelArg> Cache::s_defModelArgs;
CVector<LineArg> Cache::s_defLineArgs;

CMap<LineTag, LineArg> Cache::s_lineArgMap;
CMap<int, ModelArg> Cache::s_modelArgMap;
Status Cache::s_status;
sq::Status Cache::s_sqStatus;

bool Cache::s_isNewLinesRecved = false;
bool Cache::s_isNewModelsRecved = false;

void Cache::startAsyncLoadingZhiche() {
	GLTFParser::startAsyncParsingZhiche([&] {// parsing zhiche.glb at a new thread asynchronously
		const auto& info = GLTFParser::zhicheInfo();
		s_zhicheArg.width = info.width;
		s_zhicheArg.length = info.length;
	});
}

// ToDo discard this method, remove it later
int Cache::getModelIdClosestToOrigin(bool inFrontOfZhiche, bool inSameLaneAsZhiche) {
	int ID = -1;
	auto minDistSquare = std::numeric_limits<float>::max();
	for (auto& arg : s_modelArgMap) {
		const auto& Model = arg.value;
		const auto& Pos = arg.value.pos;
		if (inFrontOfZhiche && Pos.z > 0)// SQ: z is aligned to the rear of the model
			continue;
		if (inSameLaneAsZhiche && isZhicheLaneExists() && !isInZhicheLane(Model))
			continue;
		float _distSquare = Pos.x * Pos.x + Pos.z * Pos.z;
		if (_distSquare < minDistSquare) {
			minDistSquare = _distSquare;
			ID = arg.key;
		}
	}
	return ID;
}

CVector<int> Cache::getRangeModelIDs(float hRadius, float vRadius, HDir towards) {
	CVector<int> ids;
	for (auto& arg : s_modelArgMap) {
		const auto& Pos = arg.value.pos;
		if (abs(Pos.z) < vRadius) {
			if (HDir::Left == towards && Pos.x < 0) {
				if (-Pos.x < hRadius)
					ids << arg.key;
			} else if (HDir::Right == towards && Pos.x > 0) {
				if (Pos.x < hRadius)
					ids << arg.key;
			}
		}
	}
	return ids;
}

bool Cache::isZhicheLaneExists() {
	const auto& eqL = lineArg(LineTag::L).eq;
	const auto& eqR = lineArg(LineTag::R).eq;
	return eqL.isValid() && eqR.isValid();
}

bool Cache::isInZhicheLane(const ModelArg& modelArg) {
	const auto& eqL = lineArg(LineTag::L).eq;
	const auto& eqR = lineArg(LineTag::R).eq;
	bool laneExists = eqL.isValid() && eqR.isValid();
	if (!laneExists)
		return false;
	float halfModelWidth = modelArg.width / 2;
	if (halfModelWidth == 0)// if no width data
		halfModelWidth = GLTFParser::info(modelArg.type).width / 2;
	const auto& Pos = modelArg.pos;
	float lX = GeoUtil::getXByEquation(eqL, Pos.z);
	float rX = GeoUtil::getXByEquation(eqR, Pos.z);
	if (Pos.x - halfModelWidth > lX && Pos.x + halfModelWidth < rX)
		return true;
	return false;
}

void Cache::setSizeForModels(CVector<ModelArg>& modelArgs) {
	for (auto& modelArg : modelArgs) {
		if (modelArg.width > 0.1 && modelArg.length > 0.1)// already set valid size
			continue;
		const auto& info = GLTFParser::info(modelArg.type);
		modelArg.width = info.width;
		modelArg.length = info.length;
	}
}

void Cache::setTagsForLines(CVector<LineArg>& lines) {
	if (lines.empty())
		return;
	std::sort(lines.begin(), lines.end(), [](const LineArg& a, const LineArg& b) { return a.eq.C[0] < b.eq.C[0]; });// sort C0 ASC
	int rightLinesNum = 0;
	for (auto& line : lines) {
		if (line.eq.C[0] < 0) ++rightLinesNum;
		else break;
	}
	int leftLinesNum = lines.size() - rightLinesNum;
	for (int i = 0; i < leftLinesNum; i++) {
		auto& tag = lines[rightLinesNum + i].tag;
		switch(i) {
			case 0: tag = LineTag::L; break;
			case 1: tag = LineTag::L2; break;
			case 2: tag = LineTag::L3; break;
			case 3: tag = LineTag::L4; break;
			case 4: tag = LineTag::L5; break;
			case 5: tag = LineTag::L6; break;
			case 6: tag = LineTag::L7; break;
			case 7: tag = LineTag::L8; break;
			case 8: tag = LineTag::L9; break;
			default: break;
		}
	}
	for (int i = 0; i < rightLinesNum; i++) {
		auto& tag = lines[i].tag;
		switch(rightLinesNum - 1 - i) {
			case 0: tag = LineTag::R; break;
			case 1: tag = LineTag::R2; break;
			case 2: tag = LineTag::R3; break;
			case 3: tag = LineTag::R4; break;
			case 4: tag = LineTag::R5; break;
			case 5: tag = LineTag::R6; break;
			case 6: tag = LineTag::R7; break;
			case 7: tag = LineTag::R8; break;
			case 8: tag = LineTag::R9; break;
			default: break;
		}
	}
}